module Pan
  
  class Text
    def initialize text
      @text = text
    end
    def render_html(io)
      io << @text
    end
  end
  
  class Tag
    attr_reader :name
    def initialize name
      @name = name
      @attrs = {}
      @children = []
    end
    def add_child(child)
      @children << child
      self
    end
    def attrs(attrs={})
      @attrs = attrs
      self
    end
    def render_html(io)
      io << "<"
      io << @name
      @attrs.each do |k,v| 
        if k == 'style' or k == :style
          io << " style=\""
          v.each do |k,v|
            io << "#{k}: #{v};"
          end
          io << "\""
        else
          io << " #{k}=\"#{v}\""
        end
      end
      #if @children.size == 0
      #  io << "/>"
      #else
      io << ">"
      @children.each do |child|
        child.render_html(io)
      end
      io << "</#{@name}>"
      #end
      io.string
    end
  end
  
  class Root < Tag
    def initialize 
      @children = []
    end
    def name
      "@the-root"
    end
    def render_html
      @io = StringIO.new
      @children.each do |child|
        child.render_html(@io)
      end
      @io.string
    end
  end
    
  class HtmlCanvas
    attr_reader :root, :callbacks
    def initialize(callbacks)
      @callbacks = callbacks
      @current_tag = [Root.new] # stack
      @root = current_tag
    end
    
    def render_inline(component)
      direct {|html| component.render_on(html)}
    end
    
    def direct
      tmp_canvas = self.class.new(@callbacks)
      yield tmp_canvas
      tmp_canvas.to_html      
    end

    def render(component)
      # puts "rendering component: #{component.class}"
      if component.respond_to?(:render_content_on)
        component.render_content_on(self) 
      else
        component.render_on(self) 
      end
    end
    
    def session
      Dynamic.current_session
    end
    
    def session_id
      session.session_id
    end
    
    def base_url
      session.base_url
    end

    def application_url
      session.application_url
    end

    def build_query(id)
      if id
        id = "_#{id}" 
      else
        id = ""
      end
      base_url = Dynamic.current_req.base_url
      "#{application_url}/#{session_id}#{Dynamic.current_viewid}#{id}"
    end
    
    ### Callbacks
    
    def register_callback(proc)
      cb = Callback.new(proc)
      @callbacks.register(cb)
    end
    
    def register_ajax_callback(proc)
      cb = AjaxCallback.new(proc)
      @callbacks.register(cb)
    end
    
    def register_raw_callback(proc)
      cb = RawCallback.new(proc)
      @callbacks.register(cb)
    end
    
    def register_form_callback(ids,proc)
      cb = FormCallback.new(proc)
      cb.ids = ids
      @callbacks.register(cb)
    end

    def url_for_action(&proc)
      id = register_callback(proc)
      build_query(id)
    end
    
    ### Links
    
    def link(name, &proc)
      id = register_callback(proc)
      _tag(Tag.new("a"),{ "href"=>build_query(id)}) {lit(name)}
    end
    
    def img_link(attrs, &proc)
      id = register_callback(proc)
      _tag(Tag.new("a"),{ "href"=>build_query(id)}) {
        img(attrs)
      }
    end
    
    def raw_link(name, &proc)
      id = register_raw_callback(proc)
      query = build_query(id)
      _tag(Tag.new("a"),{ "href"=>query}) {lit(name)}
      query
    end
    
    def raw_link_silent(&proc)
      id = register_raw_callback(proc)
      build_query(id)
    end
    
    def ajax_link(name, target, &proc)
      id = register_callback(proc)
      _tag(Tag.new("a"), {"href" => "javascript:void(0)","onclick"=>"javascript: new Ajax.Updater('#{target}','http://localhost:2000/trott2007',{parameters: '#{build_query(id)[1..-1]}'})"}) {lit(name)}
    end
    
    def raw_ajax(&proc)
      id = register_ajax_callback(proc)
      build_query(id)
    end
    
    def jq_update(target, id)
      "javascript:$('##{target}').load('#{base_url}/#{session_id}#{Dynamic.current_viewid}_#{id}');"
    end
    
    def ajax_update(name, target, &proc)
      id = register_ajax_callback(proc)
      _tag(Tag.new("a"), {"href" => "javascript:void(0)","onclick"=>jq_update(target, id)}) {lit(name)} 
    end
    
    
    private
    def push_tag tag
      @current_tag.push(tag)
    end
    def pop_tag
      @current_tag.pop
    end
    def current_tag
      @current_tag[-1]
    end
    
    def _tag(tag,attrs={},&proc)
      tag.attrs(attrs)
      current_tag().add_child(tag) if current_tag 
      push_tag(tag)
      yield if proc
      pop_tag()
      tag
    end
    
    public
    
    def tag(name,attrs={},&proc)
      _tag(Tag.new(name),attrs,&proc)
    end
    
    def doctype(attrs={})
      text = '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">'
      tag = Text.new(text)
      current_tag().add_child(tag) if current_tag 
    end
    
    def doctype_transitional(attrs={})
      text = '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/2002/REC-xhtml1-20020801/DTD/xhtml1-transitional.dtd">'
      tag = Text.new(text)
      current_tag().add_child(tag) if current_tag 
    end
    
    ## tags
    def heading(txt, level=1)
      raise Exception.new("heading given block, does not make sense!") if  block_given?
      _tag(Tag.new("h#{level}")){lit(txt)}
    end
    
    #### Forms
    
    def form(attrs={},&proc)
      # or GET
      form_ids = []
      Dynamic.let(:current_form => form_ids) {
        a = {"method"=>"post","action"=>build_query("FFFF")}
        attrs.each{|key, value| a[key] = value}
        _tag(Tag.new("form"),a) do
          proc.call
        end
      }
    end
    
    def submit(attrs={},&proc)
      id = register_form_callback(Dynamic.current_form,proc)
      attrs['name'] = id unless attrs['name']
      attrs['type'] = "submit" unless attrs["type"]
      _tag(Tag.new("input"),attrs) 
    end
    
    def input(attrs={},&proc)
      id = register_callback(proc)
      Dynamic.current_form << id
      attrs['name'] = id unless attrs['name']
      attrs['type'] = 'text' unless attrs['type']
      _tag(Tag.new("input"),attrs) 
    end
    
    def textarea(attrs={},&proc)
      id = register_callback(proc)
      Dynamic.current_form << id
      attrs["name"] = id unless attrs["name"]
      value = attrs['value']
      attrs.delete('value')
      _tag(Tag.new("textarea"),attrs) {
        lit(value)
      }
    end
    
    def select(attrs={}, opts=[], &proc)
      id = register_callback(proc)
      Dynamic.current_form << id
      attrs["name"] = id unless attrs["name"]
      attrs["class"] = "select" unless attrs["class"]
      _tag(Tag.new("select"),attrs) {
        opts.each do |opt|
          if attrs["selected"] == opt
            option("value"=>opt,"selected"=>"") {
              lit(opt)
            }
          else
            option("value"=>opt) {
              lit(opt)
            }
          end
        end
      }
    end
    
    def option(attrs={},&proc)
      _tag(Tag.new("option"),attrs,&proc)
    end

    def button(text, &proc)
      form {
        submit('value'=>text) { 
          proc.call
        }
      }
    end
    
    ## standard
    def area(attrs={}, &proc)
      id = register_callback(proc)
      attrs["href"] = build_query(id)
      tag("area",attrs)
    end
    
    def h(attrs={},&proc)
      level = attrs["level"] || 1
      _tag(Tag.new("h#{level}"),attrs,&proc)
    end

    def hlink(attrs={},&proc)
      _tag(Tag.new("link"),attrs,&proc)
    end

    def anchor(attrs={},&proc)
      _tag(Tag.new("a"),attrs,&proc)
    end
    
    %w(a b iframe script div fieldset span strong em embed html body center head p legend label pre tbody span ul ol li thead table br tr th td).each do |tagname|
      class_eval <<-EOS
        def #{tagname}(attrs={},&proc)
          _tag(Tag.new("#{tagname}"),attrs,&proc)
        end
      EOS
    end

    def img(attrs={},&proc)
      attrs['alt'] = '' unless attrs.has_key?('alt')
      _tag(Tag.new("img"),attrs,&proc)
    end
    ## special cases
    def title(title)
      _tag(Tag.new("title")) {lit(title)}
    end
    def hr
      _tag(Tag.new("hr"))
    end
    def nbsp
      lit("&nbsp;")
    end
    ## simple text
    def escape(text)
      CGI.escapeHTML(text)
    end
    def text(text)
      # puts "tag-stack: #{@current_tag.map{|ea| ea.name}.join(', ')}"
      tag = Text.new(text)
      current_tag().add_child(tag) if current_tag 
    end
    def lit(txt)
      text(txt)
    end
    def wikitext(text)
      require 'redcloth'
      lit(RedCloth.new(text).to_html)
    end

    # not pretty, I see no good way to do this in ruby
    def each_between(separator, arr ,&block)
      last = arr.size - 1
      arr.each_with_index do |ea,i|
        block.call(ea,i)
        lit(separator) if i < last
      end
    end

    def string
      raise "DO NOT USE! canvas:string, use Canvas#to_html instead"
    end
    
    def to_html
      #pp @root
      root.render_html
    end
  end
  
end
